#include "stream_builder.h"

#include "logger.h"
#include "ui_socket.h"

const std::string CStreamBuilder::sIniSectionName("STREAM_BUILDER");
const unsigned int CStreamBuilder::sMaxId(16);
const unsigned int CStreamBuilder::sWindowSize(4);
const unsigned int CStreamBuilder::sResizeInc(255);

CStreamBuilder::CStreamBuilder(CPropertiesReader* pConfig, IStegIO* pStegIO) :
	mConfig(pConfig),
	mStegIO(pStegIO),
	mSend(sWindowSize),
	mRecv(sWindowSize)
{

}

CStreamBuilder::~CStreamBuilder()
{

}

NPacketSource::EPayloadInfo CStreamBuilder::getPacket(void*& pPacket, unsigned int& pPacketLength,
		unsigned int pMaxLength)
{
	if (pMaxLength == 0) {
		printl(LL_WARN, __FILE__, "Wrong max. length: %u", pMaxLength);
		return NPacketSource::PAYLOAD_ERROR;
	}

	// resend a packet since we got no ack for sWindowSize packets
	unsigned int lSendNextId(mSend.mNextId + ((mRecv.mAck > mSend.mNextId) ? sMaxId : 0));
	if ((mRecv.mAck + sWindowSize) <= lSendNextId) {
		if ((lSendNextId - mRecv.mAck) > sWindowSize) {
			printl(LL_ERR, __FILE__, "next send id and recv ack differ by %u",
					(lSendNextId - mRecv.mAck));
		}

		timeval lCurTime;
		gettimeofday(&lCurTime, NULL);
		if (lCurTime.tv_sec == mSend.mLastTime.tv_sec) {
			if ((lCurTime.tv_usec - mSend.mLastTime.tv_usec) < 100000) {
				// don't send if not more than 100ms after last packet
				return NPacketSource::PAYLOAD_NO_DATA;
			}
		}

		// -> send packet nr. mRecv.mAck, must have packet id 0 (oldest
		// unacknowledged packet)
		//printl(0, __FILE__, "resending packet with id %u, ack %u, next send id %u", mSend.mPackets[0]->mId, mSend.mAck, mSend.mNextId);

		mSend.createPacket(pPacket, pPacketLength, 0);
		CUISocket::getInstance()->sendStreamSendInfo(mSend.mPackets[0]->mId, mSend.mAck, pPacketLength - 1, true);

		return NPacketSource::PAYLOAD_DATA;
	}

	// start new packet
	CPacket* lLast(mSend.mPackets[0]);
	for (unsigned int i = 1; i < sWindowSize; i++) {
		mSend.mPackets[i - 1] = mSend.mPackets[i];
	}
	mSend.mPackets[sWindowSize - 1] = lLast;
	lLast->mLength = 0;
	lLast->mId = mSend.mNextId;

	NStegIO::EReadState rv;
	// -1 for header
	for (; lLast->mLength < (pMaxLength - 1); lLast->mLength++) {
		if (lLast->mData.size() <= lLast->mLength) {
			lLast->mData.resize(lLast->mData.size() + sResizeInc);
		}

		rv = mStegIO->read(lLast->mData[lLast->mLength]);
		if (rv != NStegIO::READ_STATE_DATA) {
			break;
		}

		CUISocket::getInstance()->sendStreamSendData(&lLast->mData[lLast->mLength], 1);
	}

	switch (rv) {
		case NStegIO::READ_STATE_ERROR:
			printl(LL_WARN, __FILE__, "read returned error state");
			break;
		case NStegIO::READ_STATE_EOF:
			//printl(0, __FILE__, "read returned EOF");
			break;
		default:
			break;
	}

#if 0
	if (lLast->mLength == 0) {
		// we don't want to delay the other side, so we send a lonesome header
		// in case no data is available but the other side would wait on our ack
	}
#endif


	if (lLast->mId != mSend.mNextId) {
		printl(LL_ERR, __FILE__, "Id from buffer packet (%u) != sender next id (%u)",
				lLast->mId, mSend.mNextId);
	}

	mSend.createPacket(pPacket, pPacketLength, sWindowSize - 1);
	CUISocket::getInstance()->sendStreamSendInfo(mSend.mPackets[sWindowSize - 1]->mId, mSend.mAck, pPacketLength - 1, false);

	if (++mSend.mNextId >= 16) {
		mSend.mNextId = 0;
	}
	gettimeofday(&mSend.mLastTime, NULL);
	return NPacketSource::PAYLOAD_DATA;
}

void CStreamBuilder::putPacket(void* pPacket, unsigned int pPacketLength)
{
	if (!pPacket || pPacketLength == 0) {
		printl(LL_WARN, __FILE__, "Got empty packet");
		return;
	}

	unsigned char* lPacket(static_cast<unsigned char*>(pPacket));

	// get packet id and next requested send id
	unsigned int lId(lPacket[0] >> 4);
	unsigned int lAck(lPacket[0] & 0x0F);

	// set ack accordingly
	// ack is allowed to be in the range of the window size, starting from the
	// next send id (decreasing)
	unsigned int lSendNextId(mSend.mNextId + ((lAck > mSend.mNextId) ? sMaxId : 0));
	if ((lAck + sWindowSize) < lSendNextId) {
		//printl(LL_WARN, __FILE__, "got wrong ack of %u", lAck);
		return;
	}
	mRecv.mAck = lAck;

	// check packet id
	// id is allowed to be in the range of the window size, starting from the
	// next awaited recv id (increasing)
	lId += (lId < mRecv.mNextId) ? sMaxId : 0; // adapt id to check range
	if (lId < mRecv.mNextId || lId >= (mRecv.mNextId + sWindowSize)) {
		// drop packet, outside window size
		//printl(LL_WARN, __FILE__, "dropping packet with id %u (%u), ack %u, next id %u, send ack %u", lId % sMaxId, lId, lAck, mRecv.mNextId, mSend.mAck);

		CUISocket::getInstance()->sendStreamRecvInfo(lId % sMaxId, lAck, pPacketLength - 1, true);
		return;
	}

	unsigned int lIndex(lId - mRecv.mNextId); // index for buffer
	unsigned int lDataLength(pPacketLength - 1); // length of data
	lId %= sMaxId; // reset id

	// copy packet data to buffer
	if (mRecv.mPackets[lIndex]->mData.size() < lDataLength) {
		mRecv.mPackets[lIndex]->mData.resize(lDataLength);
	}

	for (unsigned int i = 0; i < lDataLength; i++) {
		mRecv.mPackets[lIndex]->mData[i] = lPacket[i + 1];
	}
	mRecv.mPackets[lIndex]->mLength = lDataLength;
	mRecv.mPackets[lIndex]->mId = lId;

	//printl(0, __FILE__, "copied packet with id %u to buffer", lId);
	CUISocket::getInstance()->sendStreamRecvInfo(lId, mRecv.mAck, pPacketLength - 1, false);

	// check if packets are available to be sent to the stegio
	unsigned int lPrevId(mRecv.mPackets[0]->mId);
	if (lPrevId == sMaxId) { // first packet not filled
		//printl(0, __FILE__, "seems like a previous packet was lost");
		return;
	}

	// calculate how many packets from the buffer are in one order and can hence
	// be given to the stegio
	unsigned int lShift(1);
	for (unsigned int i = 1; i < sWindowSize; i++) {
		unsigned int lCurId(mRecv.mPackets[i]->mId);
		if (lCurId == sMaxId) {
			break;
		}
		if (lCurId != ((lPrevId + 1) % sMaxId)) {
			break;
		}
		
		lShift++;
		lPrevId = lCurId;
	}

	mRecv.mNextId += lShift;
	mRecv.mNextId %= sMaxId;
	do { // give packet data to stegio and clean buffer position afterwards
		CPacket* lLast(mRecv.mPackets[0]);
		for (unsigned int i = 0; i < lLast->mLength; i++) {
			mStegIO->write(lLast->mData[i]);
			CUISocket::getInstance()->sendStreamRecvData(&lLast->mData[i], 1);
		}

		lLast->mLength = 0;
		lLast->mId = sMaxId; // flag for unused

		for (unsigned int i = 1; i < sWindowSize; i++) {
			mRecv.mPackets[i - 1] = mRecv.mPackets[i];
		}
		mRecv.mPackets[sWindowSize - 1] = lLast;
	} while (--lShift);

	// set mSend.mAck dependend on mRecv.mNextId
	mSend.mAck = mRecv.mNextId;
}

// **********
// CSendParam
// **********

void CStreamBuilder::CSendParam::createPacket(void*& pPacket,
		unsigned int& pPacketLength, unsigned int pPacketId)
{
	if (pPacketId >= sWindowSize) {
		//printl(LL_WARN, __FILE__, "Wrong packet id %u", pPacketId);
		return;
	}

	// +1 byte for the header
	pPacketLength = mPackets[pPacketId]->mLength + 1;
	unsigned char* lPacket(getByteBuffer(pPacketLength));

	// first four bits: send packet id
	// last four bits: next recv packet id
	lPacket[0] = (mPackets[pPacketId]->mId << 4) + mAck;

	for (unsigned int i = 1; i < pPacketLength; i++) {
		lPacket[i] = mPackets[pPacketId]->mData[i - 1];
	}

	pPacket = lPacket;
}

unsigned char* CStreamBuilder::CSendParam::getByteBuffer(unsigned int pSize)
{
	if (pSize == 0) {
		delete[] mByteBuffer;
		mByteBuffer = NULL;
		mByteBufferSize = 0;
	}
	else if (pSize > mByteBufferSize) {
		delete[] mByteBuffer;
		mByteBuffer = new unsigned char[pSize];
		mByteBufferSize = pSize;
	}

	return mByteBuffer;
}

